package songgao.papers.iEC;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.Date;

public enum Controller {
	INSTANCE;

	public synchronized Boolean StartNewComputing() {
		try {
			DataManager.INSTANCE.ClearAll();
			return true;
		} catch (Exception ex) {
			return false;
		}

	}

	public synchronized String registerNewSlave() {
		Slave slave = new Slave();
		Master master = DataManager.INSTANCE.getMaster();
		master.getSlaves().add(slave);
		slave.setMaster(master);
		if (DataManager.INSTANCE.modifiedMaster(master))
			return slave.getEncodedKey();
		else
			return null;
	}

	public synchronized Boolean submitPopulation(String slaveKey, Integer generation, ArrayList<Individual> individuals, String predecessorKey) {
		Population pre = null;
		Integer preIndex = null;
		Population population = new Population();
		Parameters parameters=DataManager.INSTANCE.getParameters();
		Master master = DataManager.INSTANCE.getMaster();
		// Search the population pool for the population given to slave that is
		// submitting.
		for (Integer key = 0; key < master.getPopulationPool().size(); key++) {
			Population pop = master.getPopulationPool().get(key);
			if (pop.getEncodedKey().equals(predecessorKey) && (pop.getCurrentSlaveKey() != null && pop.getCurrentSlaveKey().equals(slaveKey))) {
				preIndex = key;
				pre = pop;
				break;
			}
		}

		if (pre == null)
			return false;
		if (generation > parameters.getMaxGeneration())
			return false;

		if (generation % parameters.getMigrationInterval() == 0)
			population.setWaitingForMigration(true);
		else if (generation / parameters.getMigrationInterval() - pre.getGeneration() / parameters.getMigrationInterval() > 1)
			return false;
		else
			population.setWaitingForMigration(false);

		ArrayList<String> individualKeys = new ArrayList<String>();

		for (Individual ind : individuals) {
			individualKeys.add(DataManager.INSTANCE.newIndividual(ind));
		}

		population.setGeneration(generation);
		population.setIndividualKeys(individualKeys);

		population.setCurrentSlaveKey(null);
		population.setPredecessorKey(pre.getEncodedKey());
		master.getPopulationPool().set(preIndex, population);
		if (!DataManager.INSTANCE.modifiedMaster(master))
			return false;
		this.inspector();
		return true;
	}

	public synchronized Population getPopulation(String slaveKey) {
		Master master=DataManager.INSTANCE.getMaster();
		ArrayList<Slave> slaves = new ArrayList<Slave>();
		Slave requester = null;
		for (Slave slave : master.getSlaves()) {
			slaves.add(slave);
			if (slave.getEncodedKey().equals(slaveKey))
				requester = slave;
		}
		if (requester == null)
			return null;
		Comparator<Slave> slaveComparator = Collections.reverseOrder(new Comparator<Slave>() {

			@Override
			public int compare(Slave o1, Slave o2) {
				return o1.getPerformanceMeasureByTracedWorkFinishedGenerations() - o2.getPerformanceMeasureByTracedWorkFinishedGenerations();
			}
		});
		Collections.sort(slaves, slaveComparator);
		int slaveRank = Collections.binarySearch(slaves, requester, slaveComparator) + 1;

		ArrayList<Population> populations = new ArrayList<Population>();
		// choose candidate populations that can be given to a slave.
		for (Population pop : master.getPopulationPool()) {
			if (pop.getCurrentSlaveKey() == null // the population shouldn'd have been given to another slave currently.
					&& (0 == pop.getGeneration() || !pop.getWaitingForMigration()) // the population shouldn't be at the migration point.
					&& pop.getGeneration() < DataManager.INSTANCE.getParameters().getMaxGeneration() // the population shouldn't have finished the evolution.
			)
				populations.add(pop);
		}
		if (populations.size() == 0)
			return null;
		Collections.sort(populations, new Comparator<Population>() {

			@Override
			public int compare(Population o1, Population o2) {
				return o2.getGeneration() - o1.getGeneration();
			}
		});
		Population r = populations.get(slaveRank * (populations.size() - 1) / slaves.size());
		requester.setPopulationKey(r.getEncodedKey());
		requester.setStartingTime(new Date());
		r.setCurrentSlaveKey(requester.getEncodedKey());
		if (DataManager.INSTANCE.modifiedMaster(master))
			return r;
		else
			return null;
	}

	private void checkTimeout() {
		ArrayList<Slave> timeoutSlaves = new ArrayList<Slave>();
		Master master=DataManager.INSTANCE.getMaster();
		for (Slave slave : master.getSlaves())
			if ((null != slave.getStartingTime()) && (new Date().getTime() - slave.getStartingTime().getTime() > DataManager.INSTANCE.getParameters().getTaskTimeoutLimit()))
				timeoutSlaves.add(slave);
		for (Slave slave : timeoutSlaves) {
			for (Population pop : master.getPopulationPool())
				if (pop.getEncodedKey() == slave.getPopulationKey()) {
					pop.setCurrentSlaveKey(null);
					break;
				}
			master.getSlaves().remove(slave);
		}
		DataManager.INSTANCE.modifiedMaster(master);
	}

	private void checkMigration() {
		Master master=DataManager.INSTANCE.getMaster();
		for (Population pop : master.getPopulationPool())
			if (pop.getWaitingForMigration() == false)
				return;

		MigrationStrategies.INSTANCE.getMigrationStrategy(DataManager.INSTANCE.getParameters().getMigrationStrategyName()).migrate(master.getPopulationPool());

		for (Population pop : master.getPopulationPool())
			pop.setWaitingForMigration(false);

		DataManager.INSTANCE.modifiedMaster(master);
	}

	private void inspector() {
		this.checkTimeout();
		this.checkMigration();
	}

}